package maple.core.applications;

import jade.lang.acl.ACLMessage;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import maple.core.Application;
import maple.lang.Command;
import maple.lang.Language;
import maple.util.Actuator;
import maple.util.Context;
import maple.util.Planner;
import maple.util.Profile;
import maple.util.Service;
import maple.util.Task;
import maple.util.Timestamp;
import maple.util.Tuple;
import maple.util.tasks.ApplicationTask;
import maple.util.tasks.MasterTask;

public class ApplicationTemplate extends Application {
    private static final long serialVersionUID = 2937337737026013474L;
    
    private final long DELAY_OFF = 10000;
    private final long WAIT_TIME = 600000;

    private final String HP_COMMAND = Language.Instruction.SELECT.toString()
            + Language.DELIMITER
            + Service.Property.NAME.toString()
            + Language.ASSIGN
            + Language.QUOTE
            + Language.Instruction.INTERPRET.toString()
            + Language.QUOTE
            + Language.DELIMITER
            + Service.Property.TYPE.toString()
            + Language.ASSIGN
            + Language.QUOTE
            + Context.Type.HUMANPRESENCE.toString()
            + Language.QUOTE;

    private String _hpagent_name;

    private Map<Actuator.Type, Boolean> _off = new HashMap<Actuator.Type, Boolean>();
    private long _touched_time;

    public ApplicationTemplate() {
        super(600, 0);

        // from Consumer
        _preconditions.add(Context.Type.MOTIONLEVEL.toString());

        _preconditions.add(Context.Type.ACSTATE.toString());
        _preconditions.add(Context.Type.LAMPSTATE.toString());
        _preconditions.add(Context.Type.PRINTERSTATE.toString());
        _preconditions.add(Context.Type.MONITORSTATE.toString());

        _postcondition = ApplicationTemplate.class.getSimpleName();

        // from Application
        _tuples = new Tuple[(_preconditions.size() + 1) * _size];

        _hpagent_name = null;

        _off.put(Actuator.Type.AIR_CONDITIONER, false);
        _off.put(Actuator.Type.LAMP, false);
        _off.put(Actuator.Type.PRINTER, false);
        _off.put(Actuator.Type.MONITOR, false);

        _touched_time = 0;
    }

    protected void setup() {
        super.setup();

        String master = findMaster();
        if (master != null) {
            sendMessage(ACLMessage.REQUEST, master, HP_COMMAND, null, null);
        } else {
            doDelete();
        }
    }

    protected void actionInform(final ACLMessage message) {
        _logger.info(message.getSender().getLocalName() + Language.DELIMITER + message.getContent());

        String command = message.getUserDefinedParameter(Task.Property.COMMAND.toString());
        if (command == null) {
            String content = message.getContent();
            if (!content.equals(Command.Message.NOT_FOUND.toString())) {
                String[] tokens = content.split(Language.DELIMITER);
                _hpagent_name = tokens[0];
            }
        } else {
            super.actionInform(message);
        }
    }

    protected void subscribe(final ApplicationTask task) {
        long current = System.currentTimeMillis();

        // Locations index:
        // 0: Motion sensor
        // 1: Air conditioner
        // 2: Lamp
        // 3: Printer
        // 4: Monitor

        Vector<String> locations = task.getLocations();
        Vector<Tuple> states = new Vector<Tuple>();
        for (int i = 0; i < locations.size(); ++i) {
            Tuple tuple = _perceptions.get(locations.get(i) + Language.DELIMITER + _preconditions.get(i));
            if (tuple != null && tuple.isValid() && (tuple.getTimestamp() - current) < 5 * INTERVAL) {
                states.add(tuple);
            }
        }

        if (states.size() == locations.size() && _hpagent_name != null) {
            String ac_state = states.get(1).getValue();
            String lamp_state = states.get(2).getValue();
            String printer_state = states.get(3).getValue();
            String monitor_state = states.get(4).getValue();

            presence(locations.get(0));
            Tuple tuple = _perceptions.get(locations.get(0) + Language.DELIMITER + Context.Type.HUMANPRESENCE.toString());
            
            if (tuple != null) {
                if (states.get(0).getTimestamp() > _touched_time) {

                    String presence = tuple.getValue();
                    if (presence.equals(Context.HumanPresence.FALSE.toString())) {
                        boolean isDelayOff = false;

                        // Air conditioner
                        if (ac_state.equals(Context.ACState.ON.toString())) {
                            touch(task.getMaster(), locations.get(1), Actuator.Type.AIR_CONDITIONER);

                            if (_off.get(Actuator.Type.AIR_CONDITIONER)) {
                                System.out.println(Timestamp.Default.format(new Date(current)) + Language.DELIMITER
                                        + "AC TRY AGAIN!");
                                _logger.info("AC TRY AGAIN!");

                                _off.put(Actuator.Type.AIR_CONDITIONER, false);
                                isDelayOff = false;
                            } else {
                                System.out.println(Timestamp.Default.format(new Date(current)) + Language.DELIMITER
                                        + "AC TURN OFF!");
                                _logger.info("AC TURN OFF!");

                                _off.put(Actuator.Type.AIR_CONDITIONER, true);
                                isDelayOff = true;
                            }
                        }

                        // Lamp
                        if (lamp_state.equals(Context.LampState.ON.toString())) {
                            touch(task.getMaster(), locations.get(2), Actuator.Type.LAMP);

                            if (_off.get(Actuator.Type.LAMP)) {
                                System.out.println(Timestamp.Default.format(new Date(current)) + Language.DELIMITER
                                        + "LAMP TRY AGAIN!");
                                _logger.info("LAMP TRY AGAIN!");

                                _off.put(Actuator.Type.LAMP, false);
                                isDelayOff = false;
                            } else {
                                System.out.println(Timestamp.Default.format(new Date(current)) + Language.DELIMITER
                                        + "LAMP TURN OFF!");
                                _logger.info("LAMP TURN OFF!");

                                _off.put(Actuator.Type.LAMP, true);
                                isDelayOff = true;
                            }
                        }

                        // Printer
                        if (printer_state.equals(Context.PrinterState.ON.toString())) {
                            touch(task.getMaster(), locations.get(3), Actuator.Type.PRINTER);

                            if (_off.get(Actuator.Type.PRINTER)) {
                                System.out.println(Timestamp.Default.format(new Date(current)) + Language.DELIMITER
                                        + "PRINTER TRY AGAIN!");
                                _logger.info("PRINTER TRY AGAIN!");

                                _off.put(Actuator.Type.PRINTER, false);
                                isDelayOff = false;
                            } else {
                                System.out.println(Timestamp.Default.format(new Date(current)) + Language.DELIMITER
                                        + "PRINTER TURN OFF!");
                                _logger.info("PRINTER TURN OFF!");

                                _off.put(Actuator.Type.PRINTER, true);
                                isDelayOff = true;
                            }
                        }

                        // Monitor
                        if (monitor_state.equals(Context.MonitorState.ON.toString())) {
                            touch(task.getMaster(), locations.get(4), Actuator.Type.MONITOR);

                            if (_off.get(Actuator.Type.MONITOR)) {
                                System.out.println(Timestamp.Default.format(new Date(current)) + Language.DELIMITER
                                        + "MONITOR TRY AGAIN!");
                                _logger.info("MONITOR TRY AGAIN!");

                                _off.put(Actuator.Type.MONITOR, false);
                                isDelayOff = false;
                            } else {
                                System.out.println(Timestamp.Default.format(new Date(current)) + Language.DELIMITER
                                        + "MONITOR TURN OFF!");
                                _logger.info("MONITOR TURN OFF!");

                                _off.put(Actuator.Type.MONITOR, true);
                                isDelayOff = true;
                            }
                        }

                        if (isDelayOff) {
                            _touched_time = current + DELAY_OFF;
                        } else {
                            _touched_time = current + WAIT_TIME;
                        }
                    }
                }
            }
        }
    }

    private void presence(final String location) {
        String motion_level = "";
        for (Tuple tuple : _tuples) {
            if (tuple != null && tuple.isValid()) {
                if (tuple.getType().equals(location + Language.DELIMITER + _preconditions.get(0))) {
                    motion_level = motion_level + Language.COMMA + tuple.getValue();
                }
            }
        }

        if (motion_level.length() > 0) {
            motion_level = motion_level.substring(1);

            String command = Language.Instruction.INTERPRET.toString() + Language.DELIMITER + _preconditions.get(0)
                    + Language.ASSIGN + Language.QUOTE + motion_level + Language.QUOTE;

            HashMap<String, String> parameters = new HashMap<String, String>();
            parameters.put(Task.Property.COMMAND.toString(), command);
            parameters.put(Planner.Property.POSTCONDITION.toString(), _postcondition);
            parameters.put(Profile.Property.LOCATION.toString(), location);

            sendMessage(ACLMessage.REQUEST, _hpagent_name, command, parameters, null);
        }
    }

    private void touch(final String master, final String location, final Actuator.Type actuator) {
        String command = Language.Instruction.SET.toString()
                + Language.DELIMITER
                + MasterTask.Property.GOAL.toString()
                + Language.ASSIGN
                + Language.QUOTE
                + actuator.toString()
                + Language.QUOTE
                + Language.DELIMITER
                + MasterTask.Property.INTERVAL.toString()
                + Language.ASSIGN
                + Language.QUOTE
                + "-1"
                + Language.QUOTE
                + Language.DELIMITER
                + MasterTask.Property.LOCATION.toString()
                + Language.ASSIGN
                + Language.QUOTE
                + location
                + Language.QUOTE;
        
        sendMessage(ACLMessage.REQUEST, master, command, null, null);
    }
}
